﻿using System.IO.Compression;
using System.Runtime.InteropServices;
using ApiLib.Common;
using ApiLib.Utils;
using JinianNet.JNTemplate;

namespace ApiLib.Generator.Tools;

/// <summary>
/// Swagger代码生成工具
/// </summary>
public static class SwaggerGenCodeTool
{
    private static readonly string CodeTemplateDir = "CodeGenTemplate";

    #region Build

    /// <summary>
    /// 生成请求代码的zip压缩包
    /// </summary>
    /// <param name="config"></param>
    /// <returns>zip文件地址</returns>
    /// <exception cref="MyActionException"></exception>
    public static string BuildCodeFile(GenConfig config)
    {
        var httpResult = HttpUtil.HttpGet(config.SwaggerJsonUrl);

        var content = httpResult.Content
            .Replace("$ref", "ref")
            .Replace("#/components/schemas/", "");
        var swaggerObj = content.Deserialize<SwaggerObj>();
        if (swaggerObj == null)
        {
            throw new MyActionException("解析失败，请使用OAS3");
        }

        var serviceFiles = BuildServiceCodeFile(config, swaggerObj);
        var modelFiles = BuildModelCodeFile(config, swaggerObj);
        var outPath = Generator(config, serviceFiles, modelFiles);

        var zipFile = outPath + ".zip";
        if (File.Exists(zipFile))
        {
            File.Delete(zipFile);
        }

        ZipFile.CreateFromDirectory(outPath, zipFile);
        return zipFile;
    }

    private static List<ServiceFileDto> BuildServiceCodeFile(GenConfig config, SwaggerObj swaggerObj)
    {
        var serviceFiles = new List<ServiceFileDto>();
        foreach (var tag in swaggerObj.tags)
        {
            var serviceFileDto = new ServiceFileDto
            {
                ClassName = tag.name + "Service",
                Description = tag.description,
                Namespace = config.ModuleName
            };
            var methods = new List<ServiceMethodDto>();
            foreach (var (path, value) in swaggerObj.paths)
            {
                if (path.Contains($"/{tag.name}/"))
                {
                    var methodDto = new ServiceMethodDto
                        { Url = path, ParamDescriptions = new Dictionary<string, string>() };
                    if (value.Count > 0)
                    {
                        var methodDtoHttpMethod = value.First().Key;
                        methodDto.HttpMethod = methodDtoHttpMethod;
                        var pathContent = value.First().Value;

                        var name = path.Contains("/{")
                            ? path[..path.IndexOf("/{", StringComparison.Ordinal)]
                            : path;
                        methodDto.Name = name[(name.LastIndexOf("/", StringComparison.Ordinal) + 1)..];

                        methodDto.Description = pathContent.summary?.Replace("\r\n", "") ?? "";
                        var parameters = new List<string>();
                        //处理Query参数
                        if (pathContent.parameters != null)
                        {
                            foreach (var parameter in pathContent.parameters)
                            {
                                var paramType = parameter.schema.FieldType ?? "object";

                                parameters.Add(paramType + " " + parameter.name);
                                if (parameter.@in == "query")
                                {
                                    methodDto.Query += string.IsNullOrEmpty(methodDto.Query) ? "?" : "&";
                                    methodDto.Query += parameter.name + "={" + parameter.name + "}";
                                }

                                methodDto.ParamDescriptions.Add(parameter.name, parameter.description);
                            }
                        }

                        //处理请求体
                        if (pathContent.requestBody != null)
                        {
                            var reqContent = pathContent.requestBody.content;
                            if (reqContent != null)
                            {
                                if (reqContent.ContainsKey("multipart/form-data"))
                                {
                                    parameters.Add("IBrowserFile file");
                                    methodDto.HttpMethod = "upload";
                                }
                                else
                                {
                                    var schemaRef = reqContent["application/json"].schema.@ref;
                                    parameters.Add(schemaRef + " info");
                                }
                            }
                        }

                        methodDto.Params = string.Join(",", parameters);
                        //处理返回
                        if (pathContent.responses != null)
                        {
                            var resContent = pathContent.responses["200"].content;
                            if (resContent == null)
                            {
                                methodDto.Response = "object";
                            }
                            else
                            {
                                var schemaRef = resContent["application/json"].schema.@ref;
                                schemaRef = schemaRef.Replace("BaseResponse_", "");
                                schemaRef = schemaRef.Replace("result_info_", "");
                                if (schemaRef.Contains("FileResult"))
                                {
                                    methodDto.HttpMethod = "download";
                                }
                                else
                                {
                                    if (schemaRef.Contains("ListResponse_"))
                                    {
                                        schemaRef = schemaRef.Replace("ListResponse_", "ListResponse<");
                                        schemaRef += ">";
                                    }

                                    if (schemaRef.Contains("List_"))
                                    {
                                        schemaRef = schemaRef.Replace("List_", "List<");
                                        schemaRef += ">";
                                    }

                                    methodDto.Response = schemaRef
                                        .Replace("Int32", "int")
                                        .Replace("String", "string")
                                        .Replace("Double", "double")
                                        .Replace("Long", "long");
                                }
                            }
                        }
                    }

                    methods.Add(methodDto);
                }
            }

            if (methods.Count > 0)
            {
                serviceFileDto.Methods = methods;
                serviceFiles.Add(serviceFileDto);
            }
        }

        return serviceFiles;
    }

    private static List<ModelFileDto> BuildModelCodeFile(GenConfig config, SwaggerObj swaggerObj)
    {
        var modelFiles = new List<ModelFileDto>();
        foreach (var (name, body) in swaggerObj.components.schemas)
        {
            if (name.Contains("BaseResponse_") || name.Contains("ListResponse_") || name.Contains("List_"))
            {
                continue;
            }

            if (body.properties == null)
            {
                continue;
            }

            var modelFileDto = new ModelFileDto
            {
                ClassName = name,
                Description = body.description,
                Namespace = config.ModuleName,
                Fields = new List<ModelFieldDto>()
            };
            foreach (var (key, field) in body.properties)
            {
                var fieldDto = new ModelFieldDto
                {
                    Name = key,
                    Description = field.description?.Replace("\r\n", "") ?? "",
                    Type = field.FieldType ?? field.@ref
                };
                if (field.type != "string" && field.nullable)
                {
                    fieldDto.Type += "?";
                }

                modelFileDto.Fields.Add(fieldDto);
            }

            modelFiles.Add(modelFileDto);
        }

        return modelFiles;
    }

    #endregion

    #region 代码生成

    private static void Init()
    {
        Engine.Current.Clean();
        //jnt模板引擎全局变量
        Engine.Configure(options =>
        {
            options.TagPrefix = "${";
            options.TagSuffix = "}";
            options.TagFlag = '$';
            options.Data.Set("dollar", "$"); //特殊标签替换
            options.OutMode = OutMode.Auto;
            options.EnableCache = true;
        });
    }

    /// <summary>
    /// 代码生成
    /// </summary>
    /// <param name="config"></param>
    /// <param name="services"></param>
    /// <param name="models"></param>
    /// <returns></returns>
    private static string Generator(GenConfig config, List<ServiceFileDto> services, List<ModelFileDto> models)
    {
        Init();
        var genCodes = new List<GenCode>();
        foreach (var serviceFile in services)
        {
            genCodes.Add(GeneratorService(serviceFile));
        }

        foreach (var modelFile in models)
        {
            genCodes.Add(GeneratorModel(modelFile));
        }

        var names = services.Select(ser => ser.ClassName).ToList();
        genCodes.Add(GeneratorHttpService(config, names));

        var outPath = Path.Combine(AppContext.BaseDirectory, "CodeGen", config.ModuleName);
        if (Directory.Exists(outPath))
        {
            Directory.Delete(outPath, true);
        }

        foreach (var item in genCodes)
        {
            item.Path = Path.Combine(outPath, item.Path);
            WriteAndSave(item.Path, item.Code);
        }

        return outPath;
    }

    private static GenCode GeneratorService(ServiceFileDto serviceFieldDto)
    {
        var tpl = ReadTemplate(CodeTemplateDir, "TplService.txt");
        tpl.Set("service", serviceFieldDto);
        var fullPath = Path.Combine("Service", serviceFieldDto.ClassName + ".cs");
        return new GenCode { Path = fullPath, Code = tpl.Render() };
    }

    private static GenCode GeneratorModel(ModelFileDto modelFile)
    {
        var tpl = ReadTemplate(CodeTemplateDir, "TplModel.txt");
        tpl.Set("model", modelFile);
        var fullPath = Path.Combine("Models", modelFile.ClassName + ".cs");
        return new GenCode { Path = fullPath, Code = tpl.Render() };
    }

    private static GenCode GeneratorHttpService(GenConfig config, List<string> names)
    {
        var tpl = ReadTemplate(CodeTemplateDir, "TplHttpService.txt");
        tpl.Set("namespace", config.ModuleName);
        tpl.Set("names", names);
        var fullPath = Path.Combine("Service", "QinHttpService.cs");
        return new GenCode { Path = fullPath, Code = tpl.Render() };
    }

    /// <summary>
    /// 读取Jn模板
    /// </summary>
    /// <param name="dirPath"></param>
    /// <param name="tplName"></param>
    /// <returns></returns>
    private static ITemplate ReadTemplate(string dirPath, string tplName)
    {
        var path = AppContext.BaseDirectory;
        var fullName = Path.Combine(path, "wwwroot", dirPath, tplName);
        if (!File.Exists(fullName))
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"未找到路径{fullName}");
            throw new Exception("未找到模板");
        }

        return Engine.LoadTemplate(fullName);
    }

    #endregion

    /// <summary>
    /// 写文件
    /// </summary>
    /// <param name="path">完整路径带扩展名的</param>
    /// <param name="content"></param>
    private static void WriteAndSave(string path, string content)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            path = path.Replace("\\", "/").Replace("//", "/");
        }

        var outPath = Path.GetDirectoryName(path);
        if (outPath != null && !Directory.Exists(outPath))
        {
            Directory.CreateDirectory(outPath);
        }

        Console.WriteLine("开始写入文件，Path=" + path);
        try
        {
            //实例化一个文件流--->与写入文件相关联
            using var fs = new FileStream(path, FileMode.Create, FileAccess.Write);
            //实例化一个StreamWriter-->与fs相关联
            using var sw = new StreamWriter(fs);
            //开始写入
            sw.Write(content);
            //清空缓冲区
            sw.Flush();
            //关闭流
            sw.Close();
            fs.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine("写入文件出错了:" + ex.Message);
        }
    }
}

/// <summary>
/// 
/// </summary>
public class GenCode
{
    /// <summary>
    /// 路径
    /// </summary>
    public string Path { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public string Code { get; set; }
}

/// <summary>
/// 配置
/// </summary>
public class GenConfig
{
    /// <summary>
    /// 导出代码的模块名
    /// </summary>
    public string ModuleName { get; set; }

    /// <summary>
    /// swagger.json地址
    /// </summary>
    public string SwaggerJsonUrl { get; set; }
}

#region JN obj

/// <summary>
/// 类文件
/// </summary>
public class ClassFileDto
{
    /// <summary>
    /// 命名空间
    /// </summary>
    public string Namespace { get; set; }

    /// <summary>
    /// 文件名
    /// </summary>
    public string ClassName { get; set; }

    /// <summary>
    /// 描述
    /// </summary>
    public string Description { get; set; }
}

/// <summary>
/// 接口服务文件
/// </summary>
public class ServiceFileDto : ClassFileDto
{
    /// <summary>
    /// 方法
    /// </summary>
    public List<ServiceMethodDto> Methods { get; set; }
}

/// <summary>
/// 模型
/// </summary>
public class ModelFileDto : ClassFileDto
{
    /// <summary>
    /// 字段
    /// </summary>
    public List<ModelFieldDto> Fields { get; set; }
}

/// <summary>
/// 服务方法
/// </summary>
public class ServiceMethodDto
{
    /// <summary>
    /// 返回类型
    /// </summary>
    public string Response { get; set; }

    /// <summary>
    /// 方法名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 参数
    /// </summary>
    public string Params { get; set; }

    /// <summary>
    /// 地址
    /// </summary>
    public string Url { get; set; }

    /// <summary>
    /// http方法
    /// </summary>
    public string HttpMethod { get; set; }

    /// <summary>
    /// 描述
    /// </summary>
    public string Description { get; set; }

    /// <summary>
    /// 参数注释
    /// </summary>
    public Dictionary<string, string> ParamDescriptions { get; set; }

    /// <summary>
    /// query参数
    /// </summary>
    public string Query { get; set; }

    /// <summary>
    /// 参数
    /// </summary>
    public string ParamInfo => Params.Contains("info") ? "info" : "\"\"";
}

/// <summary>
/// 模型属性
/// </summary>
public class ModelFieldDto
{
    /// <summary>
    /// 类型
    /// </summary>
    public string Type { get; set; }

    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 文档
    /// </summary>
    public string Description { get; set; }
}

#endregion

#region SwaggerObj

/// <summary>
/// swagger对象
/// </summary>
public class SwaggerObj
{
    /// <summary>
    /// 标签
    /// </summary>
    public List<SwaggerTag> tags { get; set; }

    /// <summary>
    /// 组件
    /// </summary>
    public SwaggerComponents components { get; set; }

    /// <summary>
    /// &amp;lt;地址,&amp;lt;请求方法,内容&amp;gt;&amp;gt;
    /// </summary>
    public Dictionary<string, Dictionary<string, SwaggerPathContent>> paths { get; set; }
}

/// <summary>
/// swagger路径对象
/// </summary>
public class SwaggerPathContent
{
    /// <summary>
    /// 
    /// </summary>
    public string[] tags { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public string? summary { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public string operationId { get; set; }

    /// <summary>
    /// 请求体
    /// </summary>
    public SwaggerRequestBody? requestBody { get; set; }

    /// <summary>
    /// 参数
    /// </summary>
    public List<SwaggerRequestParameter>? parameters { get; set; }

    /// <summary>
    /// 返回
    /// </summary>
    public Dictionary<string, SwaggerRequestBody>? responses { get; set; }
}

/// <summary>
/// swagger请求体
/// </summary>
public class SwaggerRequestBody
{
    /// <summary>
    /// 描述
    /// </summary>
    public string description { get; set; }

    /// <summary>
    /// 内容
    /// </summary>
    public Dictionary<string, SwaggerRequestContentSchema>? content { get; set; }
}

/// <summary>
/// swagger请求体内容
/// </summary>
public class SwaggerRequestContentSchema
{
    /// <summary>
    /// 
    /// </summary>
    public SwaggerRequestContent schema { get; set; }
}

/// <summary>
/// 请求内容
/// </summary>
public class SwaggerRequestContent
{
    /// <summary>
    /// $ref
    /// </summary>
    public string @ref { get; set; }

    /// <summary>
    /// 类型
    /// </summary>
    public string type { get; set; }

    /// <summary>
    /// 属性
    /// </summary>
    public Dictionary<string, SwaggerRequestParameterSchema> properties { get; set; }
}

/// <summary>
/// 请求参数
/// </summary>
public class SwaggerRequestParameter
{
    /// <summary>
    /// 名称
    /// </summary>
    public string name { get; set; }

    /// <summary>
    /// path,
    /// </summary>
    public string @in { get; set; }

    /// <summary>
    /// 描述
    /// </summary>
    public string description { get; set; }

    /// <summary>
    /// 必填
    /// </summary>
    public bool required { get; set; }

    /// <summary>
    /// 请求Schema
    /// </summary>
    public SwaggerRequestParameterSchema schema { get; set; }
}

/// <summary>
/// 请求参数Schema
/// </summary>
public class SwaggerRequestParameterSchema
{
    /// <summary>
    /// 类型
    /// </summary>
    public string type { get; set; }

    /// <summary>
    /// 格式
    /// </summary>
    public string format { get; set; }

    /// <summary>
    /// 字段类型
    /// </summary>
    public string? FieldType => type switch
    {
        "integer" => format switch
        {
            "int16" => "short",
            "int64" => "long",
            _ => "int"
        },
        "string" => "string",
        "boolean" => "bool",
        _ => default
    };
}

public class SwaggerTag
{
    public string name { get; set; }
    public string description { get; set; }
}

public class SwaggerComponents
{
    public Dictionary<string, SwaggerComponent> schemas { get; set; }
}

public class SwaggerComponent
{
    /// <summary>
    /// 类型
    /// </summary>
    public string type { get; set; }

    /// <summary>
    /// 类描述
    /// </summary>
    public string description { get; set; }

    public bool additionalProperties { get; set; }

    /// <summary>
    /// 属性
    /// </summary>
    public Dictionary<string, SwaggerComponentField>? properties { get; set; }
}

public class SwaggerComponentField : SwaggerRequestParameterSchema
{
    /// <summary>
    /// 描述
    /// </summary>
    public string? description { get; set; }

    /// <summary>
    /// 是否可以为空
    /// </summary>
    public bool nullable { get; set; }

    public string @ref { get; set; }
}

#endregion